/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.handler;

import java.util.ArrayList;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.MoreLikeThisParams;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.component.FacetComponent;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryRequestBase;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.BeforeClass;
import org.junit.Test;

/** TODO -- this needs to actually test the results/query etc */
public class MoreLikeThisHandlerTest extends SolrTestCaseJ4 {
  @BeforeClass
  public static void moreLikeThisBeforeClass() throws Exception {
    initCore("solrconfig.xml", "schema.xml");
  }

  @Test
  public void testInterface() {
    SolrCore core = h.getCore();

    ModifiableSolrParams params = new ModifiableSolrParams();

    assertU(
        adoc(
            "id",
            "42",
            "name",
            "Tom Cruise",
            "subword",
            "Top Gun",
            "subword",
            "Risky Business",
            "subword",
            "The Color of Money",
            "subword",
            "Minority Report",
            "subword",
            "Days of Thunder",
            "subword",
            "Eyes Wide Shut",
            "subword",
            "Far and Away",
            "foo_ti",
            "10"));
    assertU(
        adoc(
            "id",
            "43",
            "name",
            "Tom Hanks",
            "subword",
            "The Green Mile",
            "subword",
            "Forest Gump",
            "subword",
            "Philadelphia Story",
            "subword",
            "Big",
            "subword",
            "Cast Away",
            "foo_ti",
            "10"));
    assertU(
        adoc(
            "id",
            "44",
            "name",
            "Harrison Ford",
            "subword",
            "Star Wars",
            "subword",
            "Indiana Jones",
            "subword",
            "Patriot Games",
            "subword",
            "Regarding Henry"));
    assertU(
        adoc(
            "id",
            "45",
            "name",
            "George Harrison",
            "subword",
            "Yellow Submarine",
            "subword",
            "Help",
            "subword",
            "Magical Mystery Tour",
            "subword",
            "Sgt. Peppers Lonley Hearts Club Band"));
    assertU(
        adoc(
            "id",
            "46",
            "name",
            "Nicole Kidman",
            "subword",
            "Batman",
            "subword",
            "Days of Thunder",
            "subword",
            "Eyes Wide Shut",
            "subword",
            "Far and Away"));
    assertU(commit());

    params.set(MoreLikeThisParams.MLT, "true");
    params.set(MoreLikeThisParams.SIMILARITY_FIELDS, "name,subword");
    params.set(MoreLikeThisParams.INTERESTING_TERMS, "details");
    params.set(MoreLikeThisParams.MIN_TERM_FREQ, "1");
    params.set(MoreLikeThisParams.MIN_DOC_FREQ, "1");
    params.set("indent", "true");

    // requires 'q' or a single content stream
    SolrException ex =
        expectThrows(
            SolrException.class,
            () -> {
              try (MoreLikeThisHandler mlt = new MoreLikeThisHandler();
                  SolrQueryRequestBase req = new SolrQueryRequestBase(core, params) {}) {
                mlt.handleRequestBody(req, new SolrQueryResponse());
              }
            });
    assertEquals(ex.getMessage(), MoreLikeThisHandler.ERR_MSG_QUERY_OR_TEXT_REQUIRED);
    assertEquals(ex.code(), SolrException.ErrorCode.BAD_REQUEST.code);

    // requires a single content stream (more than one is not supported).
    ex =
        expectThrows(
            SolrException.class,
            () -> {
              try (MoreLikeThisHandler mlt = new MoreLikeThisHandler();
                  SolrQueryRequestBase req = new SolrQueryRequestBase(core, params) {}) {
                ArrayList<ContentStream> streams = new ArrayList<>(2);
                streams.add(new ContentStreamBase.StringStream("hello"));
                streams.add(new ContentStreamBase.StringStream("there"));
                req.setContentStreams(streams);
                mlt.handleRequestBody(req, new SolrQueryResponse());
              }
            });
    assertEquals(ex.getMessage(), MoreLikeThisHandler.ERR_MSG_SINGLE_STREAM_ONLY);
    assertEquals(ex.code(), SolrException.ErrorCode.BAD_REQUEST.code);

    params.set(CommonParams.Q, "id:42");

    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(
          "morelikethis - tom cruise",
          mltreq,
          "//result/doc[1]/str[@name='id'][.='46']",
          "//result/doc[2]/str[@name='id'][.='43']");
    }

    params.set(MoreLikeThisParams.BOOST, "true");

    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(
          "morelikethis - tom cruise",
          mltreq,
          "//result/doc[1]/str[@name='id'][.='46']",
          "//result/doc[2]/str[@name='id'][.='43']");
    }

    params.set(CommonParams.Q, "id:44");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ("morelike this - harrison ford", mltreq, "//result/doc[1]/str[@name='id'][.='45']");
    }

    // test MoreLikeThis debug
    params.set(CommonParams.DEBUG_QUERY, "true");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(
          "morelike this - harrison ford",
          mltreq,
          "//lst[@name='debug']/lst[@name='moreLikeThis']/lst[@name='44']/str[@name='rawMLTQuery']",
          "//lst[@name='debug']/lst[@name='moreLikeThis']/lst[@name='44']/str[@name='boostedMLTQuery']",
          "//lst[@name='debug']/lst[@name='moreLikeThis']/lst[@name='44']/str[@name='realMLTQuery']",
          "//lst[@name='debug']/lst[@name='moreLikeThis']/lst[@name='44']/lst[@name='explain']/str[@name='45']");
    }

    // test that qparser plugins work
    params.remove(CommonParams.DEBUG_QUERY);
    params.set(CommonParams.Q, "{!field f=id}44");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(mltreq, "//result/doc[1]/str[@name='id'][.='45']");
    }

    params.set(CommonParams.Q, "id:42");
    params.set(MoreLikeThisParams.QF, "name^5.0 subword^0.1");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(
          "morelikethis with weights",
          mltreq,
          "//result/doc[1]/str[@name='id'][.='43']",
          "//result/doc[2]/str[@name='id'][.='46']");
    }

    // test that qparser plugins work w/ the MoreLikeThisHandler
    params.set(CommonParams.QT, "/mlt");
    params.set(CommonParams.Q, "{!field f=id}44");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(mltreq, "//result/doc[1]/str[@name='id'][.='45']");
    }

    // test that debugging works (test for MoreLikeThis*Handler*)
    params.set(CommonParams.QT, "/mlt");
    params.set(CommonParams.DEBUG_QUERY, "true");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(
          mltreq,
          "//result/doc[1]/str[@name='id'][.='45']",
          "//lst[@name='debug']/lst[@name='explain']");
    }

    params.set(FacetComponent.COMPONENT_NAME, "true");
    params.set("facet.field", "name");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(
          mltreq,
          "//result/doc[1]/str[@name='id'][.='45']",
          "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='name']/int[@name='George'][.='1']");
    }
    params.set("facet.field", "{!ex=tg}name");
    params.set("fq", "{!tag=tg}name:George");
    try (SolrQueryRequest mltreq = new LocalSolrQueryRequest(core, params)) {
      assertQ(
          mltreq,
          "//result/doc[1]/str[@name='id'][.='45']",
          "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='name']/int[@name='George'][.='1']");
    }
  }

  @Test
  public void testMultifieldSimilarity() {
    SolrCore core = h.getCore();
    ModifiableSolrParams params = new ModifiableSolrParams();

    assertU(adoc("id", "1", "name", "aaa bbb ccc", "subword", "        zzz"));
    assertU(adoc("id", "2", "name", "    bbb ccc", "subword", "    bbb zzz"));
    assertU(adoc("id", "3", "name", "        ccc", "subword", "aaa bbb zzz"));
    assertU(adoc("id", "4", "name", "        ccc", "subword", "    bbb    "));
    assertU(commit());

    params.set(CommonParams.QT, "/mlt");
    params.set(MoreLikeThisParams.MLT, "true");
    params.set(MoreLikeThisParams.SIMILARITY_FIELDS, "name,subword");
    params.set(MoreLikeThisParams.INTERESTING_TERMS, "details");
    params.set(MoreLikeThisParams.MIN_TERM_FREQ, "1");
    params.set(MoreLikeThisParams.MIN_DOC_FREQ, "2");
    params.set(MoreLikeThisParams.BOOST, true);
    params.set("indent", "true");

    try (SolrQueryRequestBase req = new SolrQueryRequestBase(core, params) {}) {
      ArrayList<ContentStream> streams = new ArrayList<>(2);
      streams.add(new ContentStreamBase.StringStream("bbb", "zzz"));
      req.setContentStreams(streams);

      // Make sure we have terms from both fields in the interestingTerms array and all documents
      // have been retrieved as matching.
      assertQ(
          req,
          "//lst[@name = 'interestingTerms']/float[@name = 'subword:bbb']",
          "//lst[@name = 'interestingTerms']/float[@name = 'name:bbb']",
          "//result[@name = 'response' and @numFound = '4']");
    }
  }
}
